package dai.core.base

import org.joml.Matrix4f
import org.joml.Vector3f
import org.joml.Vector4f
import org.lwjgl.opengl.GL20C
import org.lwjgl.opengl.GL20C.*
import org.lwjgl.system.MemoryStack.stackPush
import java.lang.RuntimeException

class ShaderProgram{
    var program = 0
    val uniforms = HashMap<String,Int>()

    fun initShader(vertexSource:String,fragSource:String){

        if (program < 1){
            program = GL20C.glCreateProgram()
        }

        // shader
        val vertexShaderId = createShader(vertexSource, GL20C.GL_VERTEX_SHADER)
        val fragShaderId = createShader(fragSource, GL20C.GL_FRAGMENT_SHADER)

        // link
        GL20C.glLinkProgram(program)
        if (GL20C.glGetProgrami(program, GL20C.GL_LINK_STATUS) == 0){
            throw RuntimeException("error link shader:${GL20C.glGetProgramInfoLog(program,1024)}")
        }

        // detach shader
        if (vertexShaderId != 0){
            GL20C.glDetachShader(program, vertexShaderId)
        }
        if (fragShaderId != 0){
            GL20C.glDetachShader(program, fragShaderId)
        }

        GL20C.glValidateProgram(program)
        if (GL20C.glGetProgrami(program, GL20C.GL_VALIDATE_STATUS) == 0){
            MyLog.info("Warning validating shader code:${GL20C.glGetProgramInfoLog(program, 1024)}")
        }
    }

    private fun createShader(shaderSource:String, shaderType:Int):Int{
        val shaderId = GL20C.glCreateShader(shaderType)
        if (shaderId == 0){
            throw RuntimeException("error shader:${shaderSource} error!")
        }
        GL20C.glShaderSource(shaderId, shaderSource)
        GL20C.glCompileShader(shaderId)

        if (GL20C.glGetShaderi(shaderId, GL20C.GL_COMPILE_STATUS) == 0){
            throw RuntimeException("Error compile shader code:${GL20C.glGetShaderInfoLog(shaderId,1024)}")
        }
        GL20C.glAttachShader(program, shaderId)
        return shaderId
    }

    fun bind(){
        GL20C.glUseProgram(program)
    }
    fun unbind(){
        GL20C.glUseProgram(0)
    }
    fun cleanUp(){
        unbind()
        if (program != 0){
            GL20C.glDeleteProgram(program)
            program = 0
        }
    }

    fun createUniform(name:String){
        val location = glGetUniformLocation(program,name)
        if (location < 0){
            throw RuntimeException("could not find uniform:${name}")
        }
        uniforms.put(name,location)
    }

    fun setUniform(name:String,value:Matrix4f){
        stackPush().use {stack->
            val fb = stack.mallocFloat(16)
            value.get(fb)
            glUniformMatrix4fv(uniforms[name]!!,false,fb)
        }
    }
    fun setUniform(name:String,value:Int){
        glUniform1i(uniforms[name]!!,value)
    }
    fun setUniform(name:String,value:Float){
        glUniform1f(uniforms[name]!!,value)
    }
    fun setUniform(name:String,value:Vector3f){
        glUniform3f(uniforms[name]!!,value.x,value.y,value.z)
    }
    fun setUniform(name:String,value: Vector4f){
        glUniform4f(uniforms[name]!!,value.x,value.y,value.z,value.w)
    }
}